#!/usr/bin/env python

# simple-glade-codegen.py
# A code generator that uses pygtk, glade and SimpleGladeApp.py
# Copyright (C) 2004 Sandino Flores Moreno

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import sys, os, re, codecs
import tokenize, shutil, time
import xml.sax
from xml.sax._exceptions import SAXParseException

header_format = """\
#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module %(module)s.py
# Autogenerated from %(glade)s
# Generated on %(date)s

# Warning: Do not delete or modify comments related to context
# They are required to keep user's code

import os, gtk
from SimpleGladeApp import SimpleGladeApp

glade_dir = ""

# Put your modules and data here

# From here through main() codegen inserts/updates a class for
# every top-level widget in the .glade file.

"""

class_format = """\
class %(class)s(SimpleGladeApp):
%(t)sdef __init__(self, glade_path="%(glade)s", root="%(root)s", domain=None):
%(t)s%(t)sglade_path = os.path.join(glade_dir, glade_path)
%(t)s%(t)sSimpleGladeApp.__init__(self, glade_path, root, domain)

%(t)sdef new(self):
%(t)s%(t)s#context %(class)s.new {
%(t)s%(t)sprint "A new %(class)s has been created"
%(t)s%(t)s#context %(class)s.new }

%(t)s#context %(class)s custom methods {
%(t)s#--- Write your own methods here ---#
%(t)s#context %(class)s custom methods }

"""

callback_format = """\
%(t)sdef %(handler)s(self, widget, *args):
%(t)s%(t)s#context %(class)s.%(handler)s {
%(t)s%(t)sprint "%(handler)s called with self.%%s" %% widget.get_name()
%(t)s%(t)s#context %(class)s.%(handler)s }

"""

creation_format = """\
%(t)sdef %(handler)s(self, str1, str2, int1, int2):
%(t)s%(t)s#context %(class)s.%(handler)s {
%(t)s%(t)swidget = gtk.Label("%(handler)s")
%(t)s%(t)swidget.show_all()
%(t)s%(t)sreturn widget
%(t)s%(t)s#context %(class)s.%(handler)s }

"""

main_format = """\
def main():
"""

instance_format = """\
%(t)s%(root)s = %(class)s()
"""
run_format = """\

%(t)s%(root)s.run()

if __name__ == "__main__":
%(t)smain()
"""

class NotGladeDocumentException(SAXParseException):
        def __init__(self, glade_writer):
                strerror = "Not a glade-2 document"
                SAXParseException.__init__(self, strerror, None, glade_writer.sax_parser)

class SimpleGladeCodeWriter(xml.sax.handler.ContentHandler):
        def __init__(self, glade_file):
                self.indent = "\t"
                self.code = ""
                self.roots_list = []
                self.widgets_stack = []
                self.creation_functions = []
                self.callbacks = []
                self.parent_is_creation_function = False
                self.glade_file = glade_file
                self.data = {}
                self.input_dir, self.input_file = os.path.split(glade_file)
                base = os.path.splitext(self.input_file)[0]
                module = self.normalize_symbol(base)
                self.output_file = os.path.join(self.input_dir, module) + ".py"
                self.sax_parser = xml.sax.make_parser()
                self.sax_parser.setFeature(xml.sax.handler.feature_external_ges, False)
                self.sax_parser.setContentHandler(self)
                self.data["glade"] = self.input_file
                self.data["module"] = module
                self.data["date"] = time.asctime()

        def normalize_symbol(self, base):
                return "_".join( re.findall(tokenize.Name, base) )

        def capitalize_symbol(self, base):
                ClassName = "[a-zA-Z0-9]+"
                base = self.normalize_symbol(base)
                capitalize_map = lambda s : s[0].upper() + s[1:]
                return "".join( map(capitalize_map, re.findall(ClassName, base)) )

        def uncapitalize_symbol(self, base):
                InstanceName = "([a-z])([A-Z])"
                action = lambda m: "%s_%s" % ( m.groups()[0], m.groups()[1].lower() )
                base = self.normalize_symbol(base)
                base = base[0].lower() + base[1:]
                return re.sub(InstanceName, action, base)

        def startElement(self, name, attrs):
                if name == "widget":
                        widget_id = attrs.get("id")
                        widget_class = attrs.get("class")
                        if not widget_id or not widget_class:
                                raise NotGladeDocumentException(self)
                        if not self.widgets_stack:
                                self.creation_functions = []
                                self.callbacks = []
                                class_name = self.capitalize_symbol(widget_id)
                                self.data["class"] = class_name
                                self.data["root"] = widget_id
                                self.roots_list.append(widget_id)
                                self.code += class_format % self.data
                        self.widgets_stack.append(widget_id)
                elif name == "signal":
                        if not self.widgets_stack:
                                raise NotGladeDocumentException(self)
                        widget = self.widgets_stack[-1]
                        signal_object = attrs.get("object")
                        if signal_object:
                                return
                        handler = attrs.get("handler")
                        if not handler:
                                raise NotGladeDocumentException(self)
                        if handler.startswith("gtk_"):
                                return
                        signal = attrs.get("name")
                        if not signal:
                                raise NotGladeDocumentException(self)
                        self.data["widget"] = widget
                        self.data["signal"] = signal
                        self.data["handler"]= handler
                        if handler not in self.callbacks:
                                self.code += callback_format % self.data
                                self.callbacks.append(handler)
                elif name == "property":
                        if not self.widgets_stack:
                                raise NotGladeDocumentException(self)
                        widget = self.widgets_stack[-1]
                        prop_name = attrs.get("name")
                        if not prop_name:
                                raise NotGladeDocumentException(self)
                        if prop_name == "creation_function":
                                self.parent_is_creation_function = True

        def characters(self, content):
                if self.parent_is_creation_function:
                        if not self.widgets_stack:
                                raise NotGladeDocumentException(self)
                        handler = content.strip()
                        if handler not in self.creation_functions:
                                self.data["handler"] = handler
                                self.code += creation_format % self.data
                                self.creation_functions.append(handler)

        def endElement(self, name):
                if name == "property":
                        self.parent_is_creation_function = False
                elif name == "widget":
                        if not self.widgets_stack:
                                raise NotGladeDocumentException(self)
                        self.widgets_stack.pop()

        def write(self):
                self.data["t"] = self.indent
                self.code += header_format % self.data
                try:
                        glade = open(self.glade_file, "r")
                        self.sax_parser.parse(glade)
                except xml.sax._exceptions.SAXParseException, e:
                        sys.stderr.write("Error parsing document\n")
                        return None
                except IOError, e:
                        sys.stderr.write("%s\n" % e.strerror)
                        return None

                self.code += main_format % self.data

                for root in self.roots_list:
                        self.data["class"] = self.capitalize_symbol(root)
                        self.data["root"] = self.uncapitalize_symbol(root)
                        self.code += instance_format % self.data

                self.data["root"] = self.uncapitalize_symbol(self.roots_list[0])
                self.code += run_format % self.data

                try:
                        self.output = codecs.open(self.output_file, "w", "utf-8")
                        self.output.write(self.code)
                        self.output.close()
                except IOError, e:
                        sys.stderr.write("%s\n" % e.strerror)
                        return None
                return self.output_file

def usage():
        program = sys.argv[0]
        print """\
Write a simple python file from a glade file.
Usage: %s <file.glade>
""" % program

def check_for_programs():
        which_command = "which %s >/dev/null"
        packages = {"diff":"diffutils", "patch":"patch"}
        for package in packages.keys():
                if os.system(which_command % package) != 0:
                        sys.stderr.write("Required program %s could not be found\n" % package)
                        sys.stderr.write("Is the package %s installed\n" % packages[package])
                        return False
        return True

def main():
        if not check_for_programs():
                return -1
        if len(sys.argv) == 2:
                code_writer = SimpleGladeCodeWriter( sys.argv[1] )
                glade_file = code_writer.glade_file
                output_file = code_writer.output_file
                output_file_orig = output_file + ".orig"
                output_file_bak = output_file + ".bak"
                short_f = os.path.split(output_file)[1]
                short_f_orig = short_f + ".orig"
                short_f_bak = short_f + ".bak"
                helper_module = os.path.join(code_writer.input_dir,SimpleGladeApp_py)
                custom_diff = "custom.diff"

                exists_output_file = os.path.exists(output_file)
                exists_output_file_orig = os.path.exists(output_file_orig)
                if not exists_output_file_orig and exists_output_file:
                        sys.stderr.write('File "%s" exists\n' % short_f)
                        sys.stderr.write('but "%s" does not.\n' % short_f_orig)
                        sys.stderr.write("That means your custom code would be overwritten.\n")
                        sys.stderr.write('Please manually remove "%s"\n' % short_f)
                        sys.stderr.write("from this directory.\n")
                        sys.stderr.write("Anyway, I\'ll create a backup for you in\n")
                        sys.stderr.write('"%s"\n' % short_f_bak)
                        shutil.copy(output_file, output_file_bak)
                        return -1
                if exists_output_file_orig and exists_output_file:
                        os.system("diff -uU1 %s %s > %s" % (output_file_orig, output_file, custom_diff) )
                        if not code_writer.write():
                                os.remove(custom_diff)
                                return -1
                        shutil.copy(output_file, output_file_orig)
                        if os.system("patch -fp0 < %s >/dev/null" % custom_diff):
                                os.remove(custom_diff)
                                return -1
                        os.remove(custom_diff)
                else:
                        if not code_writer.write():
                                return -1
                        shutil.copy(output_file, output_file_orig)
                os.chmod(output_file, 0755)
                if not os.path.isfile(helper_module):
                        open(helper_module, "w").write(SimpleGladeApp_content)
                print output_file
                return 0
        else:
                usage()
                return -1

SimpleGladeApp_py = "SimpleGladeApp.py"

SimpleGladeApp_content = """\
# SimpleGladeApp.py
# Module that provides an object oriented abstraction to pygtk and libglade.
# Copyright (C) 2004 Sandino Flores Moreno

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

try:
        import os
        import sys
        import gtk
        import gtk.glade
except ImportError:
        print "Error importing pygtk2 and pygtk2-libglade"
        sys.exit(1)

class SimpleGladeApp(dict):
        def __init__(self, glade_filename, main_widget_name=None, domain=None):
                gtk.glade.set_custom_handler(self.custom_handler)
                if os.path.isfile(glade_filename):
                        self.glade_path = glade_filename
                else:
                        glade_dir = os.path.split( sys.argv[0] )[0]
                        self.glade_path = os.path.join(glade_dir, glade_filename)
                self.glade = gtk.glade.XML(self.glade_path, main_widget_name, domain)
                if main_widget_name:
                        self.main_widget = self.glade.get_widget(main_widget_name)
                else:
                        self.main_widget = None
                self.signal_autoconnect()
                self.new()

        def signal_autoconnect(self):
                signals = {}
                for attr_name in dir(self):
                        attr = getattr(self, attr_name)
                        if callable(attr):
                                signals[attr_name] = attr
                self.glade.signal_autoconnect(signals)

        def custom_handler(self,
                        glade, function_name, widget_name,
                        str1, str2, int1, int2):
                if hasattr(self, function_name):
                        handler = getattr(self, function_name)
                        return handler(str1, str2, int1, int2)

        def __getattr__(self, data_name):
                if data_name in self:
                        data = self[data_name]
                        return data
                else:
                        widget = self.glade.get_widget(data_name)
                        if widget != None:
                                self[data_name] = widget
                                return widget
                        else:
                                raise AttributeError, data_name

        def __setattr__(self, name, value):
                self[name] = value

        def new(self):
                pass

        def on_keyboard_interrupt(self):
                pass

        def gtk_widget_show(self, widget, *args):
                widget.show()

        def gtk_widget_hide(self, widget, *args):
                widget.hide()

        def gtk_widget_grab_focus(self, widget, *args):
                widget.grab_focus()

        def gtk_widget_destroy(self, widget, *args):
                widget.destroy()

        def gtk_window_activate_default(self, widget, *args):
                widget.activate_default()

        def gtk_true(self, *args):
                return gtk.TRUE

        def gtk_false(self, *args):
                return gtk.FALSE

        def gtk_main_quit(self, *args):
                gtk.main_quit()

        def main(self):
                gtk.main()

        def quit(self):
                gtk.main_quit()

        def run(self):
                try:
                        self.main()
                except KeyboardInterrupt:
                        self.on_keyboard_interrupt()
"""

if __name__ == "__main__":
        exit_code = main()
        sys.exit(exit_code)
